package org.stephen.recommend.recommender;

import org.stephen.recommend.algorithms.AbstractRecommender;
import org.stephen.recommend.enums.LanguageType;
import org.stephen.recommend.model.HotNewsList;
import org.stephen.recommend.model.UserTags;
import org.stephen.recommend.model.ViewedArticles;
import org.stephen.recommend.service.CmsArticleService;
import org.stephen.recommend.service.TagService;
import org.stephen.recommend.thread.AsyncService;
import org.stephen.recommend.util.CommonUtils;
import org.stephen.recommend.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * ClassName: ContentBasedRecommender
 * Description: 基于内容的推荐 Content-Based
 * 思路：提取抓取进来的新闻的关键词列表（tf-idf），与每个用户的喜好关键词列表，做关键词相似度计算，取最相似的N个新闻推荐给用户。
 * Procedure: 1、Every time that the recommendation is started up(according
 * to quartz framework), the current day's coming in news will be
 * processed by class TF-IDF's getTFIDF() method to obtain their key words
 * list.And then the system go over every user and calculate the
 * similarity between every news's key words list with user's preference
 * list.After that, rank the news according to the similarities and
 * recommend them to users.
 * Date: 2020/12/8 20:37
 *
 * @author stephen.qiu
 * @version 1.0
 */
@Component
public class ContentBasedRecommender extends AbstractRecommender {
    private static final Logger logger = LoggerFactory.getLogger(ContentBasedRecommender.class);

    @Autowired
    CmsArticleService cmsArticleService;

    @Autowired
    TagService tagService;

    @Autowired
    AsyncService asyncService;

    /**
     * 针对特定用户返回推荐结果
     *
     * @param blockArticleIds 用户屏蔽的文章
     * @param userId          用户id/设备id
     * @param languageType    语言环境
     */
    @Override
    public void recommend(List<String> blockArticleIds, String userId, LanguageType languageType) {
        logger.info("----------开始基于内容推荐----------");
        /**
         * 获取等待推荐的文章，不够总推荐数量再使用基于内容推荐
         */
        String recommendArticleIds = getRecommendArticleIds(userId);
        if (recommendArticleIds == null || recommendArticleIds.split(",").length < propGetKit.getTotalRecNum()) {
            /**
             * 获取用户浏览历史
             */
            ViewedArticles viewedArticles = getViewedArticles(userId);
            if (viewedArticles == null) {
                /**
                 * 用户没有浏览历史，无法进行基于内容推荐
                 */
                logger.info("CB缺乏用户行为数据,无法进行内容推荐");
                return;
            } else if (viewedArticles.getViewedArticleTimeMap().size() == 0) {
                logger.info("用户行为数据已经过期,无法进行内容推荐");
                return;
            }
            /**
             * 获取用户浏览历史标签排序
             */
            UserTags userTags = getUserTags(userId, languageType);
            if (userTags == null) {
                logger.error("CB用户标签尚未生成");
                return;
            } else if (userTags.getUserTagPointMap().size() == 0) {
                logger.info("CF用户标签尚已经过期");
                return;
            }
            List<Map.Entry<String, Double>> userTagsListMap = userTags.getUserTagsListMap(propGetKit.getDeclineFactor());
            /**
             * 获取所有不会被推荐的文章列表
             */
            Set<String> allBlockArticleIdsSet = getAllBlockArticleIdsSet(userId, blockArticleIds);
            /**
             * 按用户感兴趣标签排序顺序获取所对应为文章
             */
            List<String> contentBasedRecommendArticleIds = getContentBasedRecommendArticlesByTags(allBlockArticleIdsSet, userTagsListMap, propGetKit.getCBRecNum(), languageType);
            /**
             * 获取内容推荐文章
             */
            logger.info("CB内容推荐文章数量:" + contentBasedRecommendArticleIds.size());
            setRecommendArticles(contentBasedRecommendArticleIds, userId);
        }
    }

    public List<String> getContentBasedRecommendArticlesByTags(Set<String> blockArticleIds, List<Map.Entry<String, Double>> userTagsListMap, int num, LanguageType languageType) {
        Map<String, Double> map = new HashMap<>(CommonUtils.initHashMapSize(num));
        here:
        for (Map.Entry<String, Double> userTagsMap : userTagsListMap) {
            String tagId = tagService.getTagIdByName(userTagsMap.getKey());
            List<String> articleIds = getTagArticleIds(tagId, languageType);
            if (articleIds != null) {
                for (String articleId : articleIds) {
                    /**
                     * 把没推荐过的文章加入推荐列表
                     */
                    boolean flag = (Utils.isEmpty(blockArticleIds) || !blockArticleIds.contains(articleId)) && (map.containsKey(articleId) || HotNewsList.getInstance().isExist(articleId, languageType));
                    if (flag) {
                        map.put(articleId, map.getOrDefault(articleId, 0.0) + userTagsMap.getValue());
                        if (map.size() >= num * num) {
                            break here;
                        }
                    }
                }
            }
        }
        if (Utils.isEmpty(map)) {
            return null;
        }
        List<Map.Entry<String, Double>> articleList = new ArrayList<>(map.entrySet());
        Collections.sort(articleList, CommonUtils.getComparator());
        List<String> list = articleList.stream().map(c -> c.getKey()).collect(Collectors.toList());
        return (list.size() <= num) ? list : list.subList(0, num);
    }
}
